package bmechat.server;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * The Server class accepts connections on a set TCP port, starts ClientHandler threads to manage incoming connections,
 * and provides services for the ClientHandlers.
 * @author david
 */
public class Server implements Runnable {

    private String serverName;
    private String serverPassword;
    private int listenPort;
    private ServerSocket serverSocket;
    private int maxClients;
    private ExecutorService pool;
    private List<ClientHandler> clients;

    /**
     * Create a server.
     * @param serverName The name the server should use.
     * @param serverPassword The admin password clients can use to gain admin rights.
     * @param listenPort The port the server should listen on.
     * @param maxClients The maximum number of clients the server should accept.
     */
    public Server(String serverName, String serverPassword, int listenPort, int maxClients) {
        this.serverName = serverName;
        this.serverPassword = serverPassword;
        this.listenPort = listenPort;
        this.maxClients = maxClients;
        clients = new ArrayList<ClientHandler>();
    }

    /**
     * Start the server.
     */
    private void setupServer() {
        try {
            this.serverSocket = new ServerSocket(listenPort);
            System.out.println("Server " + getServerName() + " started on port " + listenPort);
            pool = Executors.newFixedThreadPool(maxClients);
        } catch (IOException e) {
            System.out.println("Error while setting up the server on port " + listenPort);
        }
    }

    /**
     * Start listening for new clients. When a client connects, start a ClientHandler to serve the client.
     */
    public void run() {
        setupServer();
        while (true) {
            Socket client = null;
            try {
                client = serverSocket.accept();
                System.out.println("Client connected!");
                ClientHandler clientHandler = new ClientHandler(client, this);
                clients.add(clientHandler);
                pool.execute(clientHandler);
            } catch (IOException e) {
                System.out.println("Error while accepting connection");
            }
        }
    }

    /**
     * Get the name the server uses.
     * @return The name the server uses.
     */
    public String getServerName() {
        return serverName;
    }

    /**
     * Start the server with specified settings.
     * @param args servername serverpassword listenport.
     */
    public static void main(String args[]) {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool.execute(new Server(args[0], args[1], Integer.parseInt(args[2]), Integer.parseInt(args[3])));
    }

    /**
     * Add a client to the clientlist.
     * @param client
     */
    public void addClient(ClientHandler client) {
        clients.add(client);
    }

    /**
     * Remove a client from the clientlist. Broadcast a message to the other clients that this client disconnected.
     * @param client The client to remove.
     * @return Always returns true.
     */
    public boolean removeClient(ClientHandler client) {
        boolean succeed = clients.remove(client);
        if (succeed) {
            broadcastMessageToAllClients("#clientquit = " + client.getFormattedClientName());
        }
        return succeed;
    }

    /**
     * Send a message to all connected clients.
     * @param message The message to be sent.
     */
    public void broadcastMessageToAllClients(String message) {
        System.out.println("SERVER: " + message);
        for (ClientHandler client : clients) {
            client.sendMessageToClient(message);
        }
    }

    /**
     * Check if the given password matches the admin password.
     * @param password The password to test.
     * @return Is the password correct?
     */
    public boolean checkPassword(String password) {
        return password.equals(serverPassword);
    }

    /**
     * Kick a client from the server.
     * @param clientName The victim's name.
     * @param invoker The ClientHandler for the client kicking the victim.
     */
    public void kickClient(String clientName, ClientHandler invoker) {
        ClientHandler victim = null;
        for (ClientHandler client : clients) {
            if (client.getClientName().equals(clientName)) {
                victim = client;
            }
        }
        if (victim != null) {
            victim.disconnect();
            clients.remove(victim);
            broadcastMessageToAllClients(victim.getFormattedClientName() + " has been kicked by " + invoker.getFormattedClientName() + "!");
        }
    }

    public void killClient(String clientName) {
        ClientHandler victim = null;
        for (ClientHandler client : clients) {
            if (client.getClientName().equals(clientName)) {
                victim = client;
            }
        }
        if (victim != null) {
            victim.disconnect();
            clients.remove(victim);
        }
    }

    /**
     * Get a list of Clients
     * @param formatted When true, formatting is added to the client names (i.e. @ for admins).
     * @return A list of names of all connected clients.
     */
    public List<String> getListOfClients(boolean formatted) {
        List<String> clientList = new ArrayList<String>();
        for (ClientHandler client : clients) {
            if (formatted) {
                clientList.add(client.getFormattedClientName());
            } else {
                clientList.add(client.getClientName());
            }
        }
        return clientList;
    }

    /**
     * Get the number of clients on the server.
     * @return The number of clients on the server.
     */
    public int getNumberOfClients() {
        return clients.size();
    }

    /**
     * Method for retrieving the servers InetAdress
     * @return servers InetAddress as a String. If an error occured, the result is unknown.
     */
    public String getAddress() {
        try {
            return InetAddress.getLocalHost().toString();
        } catch (UnknownHostException ex) {
            return "unknown";
        }
    }

    public boolean warnTransfer(String clientName, int filesize, String senderIName, String filename){
        boolean clientFound = false;
        for(ClientHandler ch : clients){
            if(ch.getClientName().equals(clientName)){
                ch.broadcastFileAvailable(filesize, senderIName, filename);
                clientFound = true;
            }
        }
        return clientFound;
    }
}
